package com.yuyou.fn.educational.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.github.wens.framework.service.impl.SuperServiceImpl;
import com.github.wens.mybatisplus.examples.Example;
import com.github.wens.mybatisplus.plugins.Page;
import com.github.wens.mybatisplus.toolkit.IdWorker;
import com.google.common.collect.Collections2;
import com.google.common.collect.Multimaps;
import com.yuyou.fn.common.exception.BusException;
import com.yuyou.fn.common.rocketmq.Producer;
import com.yuyou.fn.common.util.*;
import com.yuyou.fn.educational.discount.DiscountRegClassInfo;
import com.yuyou.fn.educational.discount.DiscountRegCourseClass;
import com.yuyou.fn.educational.entity.*;
import com.yuyou.fn.educational.mapper.CourseOrderMapper;
import com.yuyou.fn.educational.service.*;
import com.yuyou.fn.educational.vo.*;
import com.yuyou.fn.settlement.entity.MainAccount;
import com.yuyou.fn.settlement.entity.PosPayRecord;
import com.yuyou.fn.settlement.service.IMainAccountService;
import com.yuyou.fn.settlement.service.INewPayRecordService;
import com.yuyou.fn.settlement.service.IPosPayService;
import com.yuyou.fn.settlement.service.ISettlementService;
import com.yuyou.fn.settlement.vo.PayWay;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * CourseOrder 表数据服务层接口实现类
 */
@Service
public class CourseOrderServiceImpl extends SuperServiceImpl<CourseOrderMapper, CourseOrder, Long> implements ICourseOrderService {


    @Resource
    private IStudentRegService studentRegService;

    @Resource
    private ICourseClassService courseClassService ;

    @Resource
    private IStudentService studentService ;

    @Resource
    private INewPayRecordService newPayRecordService ;

    @Resource
    private ISettlementService settlementService;

    @Resource
    private IDiscountCalculateService discountCalculateService;

    @Resource
    private ITeacherService teacherService;

    @Resource
    private Redisson redisson ;

    @Resource
    private IRegRuleService regRuleService;

    @Resource
    private RedisTemplate<String,String> redisTemplate;

    @Resource
    private ICourseOrderDiscountService courseOrderDiscountService;

    @Resource
    private IDiscountCodeService discountCodeService ;

    @Resource
    private IEmployeeDiscountService employeeDiscountService;

    @Resource
    private IThirdpartyDiscountCodeService thirdpartyDiscountCodeService;

    @Resource
    private IPosPayService posPayService;

    @Resource
    private IDivRegService divRegService ;

    @Resource
    private Producer producer ;

    @Resource
    private IMainAccountService mainAccountService ;


    @Override
    public Page<CourseOrder> findStudentEffectiveCourseOrderList(Long studentId, int pageNo, int pageSize) {
        Example<CourseOrder> example = CourseOrder.createExample();
        example.orderBy("createTime").desc();
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("studentId" , studentId ).andIn("chargingStatus" ,Arrays.asList(0,1,2)) ;
        return findPageByExample(example, pageNo, pageSize);
    }


    @Transactional
    @Override
    public void payCourseOrder( CourseOrder courseOrder, PayInfoOfCourseOrder payInfoOfCourseOrder ) {


        if(payInfoOfCourseOrder.getPrincipal() == null ){
            payInfoOfCourseOrder.setPrincipal( Principals.getDefaultPrincipal() );
        }




        if( 2 == courseOrder.getChargingStatus() ){
            throw new BusException("这个定单已经支付");
        }

        if( 4 == courseOrder.getChargingStatus() ){
            throw new BusException("这个定单已经撤销");
        }

        BigDecimal owing = courseOrder.getPayableAmount().subtract(courseOrder.getPaidAmount());
        if(BigDecimals.compare(payInfoOfCourseOrder.getPayAmount() , owing) > 0  ){
            throw new BusException("支付金额大于应付金额");
        }

        for(PayWay payWay : payInfoOfCourseOrder.getPayWays() ){
            //账户余额,检查余额是否足够
            if(3 == payWay.getPayWay() ){
                settlementService.decreaseBalance(courseOrder.getStudentId(),payWay.getAmount(),"购买课程") ;
            }
        }

        List<StudentReg> studentRegList = studentRegService.findStudentRegByCourseOrderId(courseOrder.getCourseOrderId());
        if( payInfoOfCourseOrder.getPayLectures() == null ){
            if(courseOrder.getRegFrom() == 1 || courseOrder.getRegFrom() == 2 ){//线上或转班
                List<PayLecture> payLectures = new ArrayList<>(studentRegList.size());
                for(StudentReg studentReg : studentRegList ){
                    PayLecture payLecture = new PayLecture();
                    payLecture.setRegId(studentReg.getRegId());
                    payLecture.setPayAmount(studentReg.getTotalAmount());
                    payLecture.setStart(studentReg.getStartAmount());
                    payLecture.setEnd(studentReg.getEndAmount());
                    payLectures.add(payLecture);
                }
                payInfoOfCourseOrder.setPayLectures(payLectures);
            }else{
                throw new BusException("缴费讲次不能为空");
            }
        }


        boolean isFirstPay = courseOrder.getChargingStatus() == 0 ;


        CourseOrder updateCourseOrder = new CourseOrder();
        updateCourseOrder.setCourseOrderId(courseOrder.getCourseOrderId());
        updateCourseOrder.setPaidAmount(courseOrder.getPaidAmount().add(payInfoOfCourseOrder.getPayAmount()));
        updateCourseOrder.setUpdateTime(TimeUtils.now());

        if( BigDecimals.compare( courseOrder.getPayableAmount() , updateCourseOrder.getPaidAmount() ) == 0   ){
            updateCourseOrder.setChargingStatus(2);
        }else{
            updateCourseOrder.setChargingStatus(1);
        }
        updateSelectiveById(updateCourseOrder);

        payInfoOfCourseOrder.setPayLectures(filterPayLectures(payInfoOfCourseOrder.getPayLectures()));

        studentRegService.changeStudentRegCharging( courseOrder.getCourseOrderId() , updateCourseOrder.getChargingStatus() , payInfoOfCourseOrder.getPayLectures() );

        newPayRecordService.savePayRecord( courseOrder.getCourseOrderId(), 0, courseOrder.getChargingStatus() == 0 ? true : false , payInfoOfCourseOrder.getPayAmount() , payInfoOfCourseOrder.getChargeCampusId() ,payInfoOfCourseOrder.getPayWays() ,JSONObject.toJSONString(payInfoOfCourseOrder.getPayLectures())  ,payInfoOfCourseOrder.getPrincipal()  ) ;


        if(payInfoOfCourseOrder.getPayAmount().compareTo(BigDecimal.ZERO) != 0 ){
            //结算
            for(StudentReg studentReg : studentRegList ){

                BigDecimal amount =  BigDecimals.calProportion(payInfoOfCourseOrder.getPayAmount(), studentReg.getTotalAmount() , courseOrder.getPayableAmount() );
                settlementService.bookIncomeForCourse(studentReg.getFinancialOwnerId() ,studentReg.getStudentId() , amount , studentReg.getClassName() +"|" + studentReg.getStudentName() , studentReg.getClassId() , studentReg.getRegId()   ) ;
            }
        }

        if(isFirstPay){
            final Principals.Principal principal1 = payInfoOfCourseOrder.getPrincipal();
            Threads.dispatch(()->{
                //第一次支付是要检查优惠码是否被使用了
                CourseOrderDiscount courseOrderDiscount = courseOrderDiscountService.findByCourseOrderIdAndDiscountId(courseOrder.getCourseOrderId(),1l);
                if(courseOrderDiscount != null ){
                    discountCodeService.updateUsedStatus(courseOrderDiscount.getDiscountCode());
                }

                Student student = studentService.findById(courseOrder.getStudentId(),"name","studentNo");
                courseOrderDiscount = courseOrderDiscountService.findByCourseOrderIdAndDiscountId(courseOrder.getCourseOrderId(),2l);
                if(courseOrderDiscount != null ){
                    EmployeeDiscountVo employeeDiscountVo = JSONObject.parseObject(courseOrderDiscount.getExtra(), EmployeeDiscountVo.class);
                    employeeDiscountService.updateUsedStatus(courseOrder.getCourseOrderId(),courseOrder.getTotalAmount() ,courseOrderDiscount.getDiscountCode(),courseOrderDiscount.getDiscountAmount(),student.getName(),student.getStudentNo(),employeeDiscountVo.getVersion(),principal1);
                }
                courseOrderDiscount = courseOrderDiscountService.findByCourseOrderIdAndDiscountId(courseOrder.getCourseOrderId(),3l);
                if(courseOrderDiscount != null ){
                    ThirdpartyDiscountCodeVo thirdpartyDiscountCodeVo = JSONObject.parseObject(courseOrderDiscount.getExtra(), ThirdpartyDiscountCodeVo.class);
                    thirdpartyDiscountCodeService.updateUsedStatus(courseOrder.getCourseOrderId(),courseOrder.getTotalAmount() ,courseOrderDiscount.getDiscountCode(),courseOrderDiscount.getDiscountAmount(),student.getName(),student.getStudentNo(),thirdpartyDiscountCodeVo.getVersion());
                }
            });
        }

    }

    private List<PayLecture> filterPayLectures(List<PayLecture> payLectures) {
        List<PayLecture> list  = new ArrayList<>(payLectures.size());
        for(PayLecture payLecture : payLectures ){
            if(payLecture.getStart() <= payLecture.getEnd() ){
                list.add(payLecture);
            }
        }
        return list;
    }

    @Transactional
    @Override
    public CourseOrder createOrder(StudentRegInfoVo studentRegInfoVo ,Principals.Principal principal) {

        Future<List<CourseClass>> futureOfCourseClassList = Threads.dispatch(()->{
            List<Long> classIds  = BeanUtils.toList( studentRegInfoVo.getRegClassInfoList(),"classId" );
            return courseClassService.findByIds(classIds,CourseClass.BASE_FIELDS);
        });

        Future<Student> futureOfStudent = Threads.dispatch(()->{
            return studentService.findById(studentRegInfoVo.getStudentId());
        });

        List<CourseClass> courseClassList = Threads.get(futureOfCourseClassList);
        Student student = Threads.get(futureOfStudent) ;

        Map<Long,RegClassInfoVo> classId2RegClassInfoMap  = BeanUtils.toMap( studentRegInfoVo.getRegClassInfoList(),"classId" );

        List<DiscountRegClassInfo> discountRegClassInfoList = new ArrayList<>(courseClassList.size());



        Set<Long> areaTeamIds = new HashSet<>();
        for(CourseClass courseClass : courseClassList){
            DiscountRegClassInfo discountRegClassInfo = new DiscountRegClassInfo();
            BeanUtils.copyProperties(discountRegClassInfo,courseClass);
            RegClassInfoVo regClassInfoVo = classId2RegClassInfoMap.get(courseClass.getClassId());

            if( ( regClassInfoVo.getStartAmount() > regClassInfoVo.getEndAmount() ) ||  regClassInfoVo.getStartAmount() < 1 ||  regClassInfoVo.getEndAmount() > courseClass.getLectureAmount()){
                throw new BusException("【"+courseClass.getClassName()+"】报名讲次有误" );
            }

            List<DivRegInfo> divRegInfos = divRegService.calDivRegInfo(courseClass.getClassName(), courseClass.getAreaTeamName(), courseClass.getBusTeamName(), courseClass.getPeriodName(), courseClass.getGradeName(), courseClass.getSubjectName(), courseClass.getLectureAmount(), courseClass.getSegmentNo());

            if(divRegInfos.isEmpty()){
                throw new BusException("分段报读设置有误");
            }

            DivRegInfo willDivRegInfo = divRegInfos.get(0);
            if(divRegInfos.size() > 1 ){
                DivRegInfo temp = null ;
                for(DivRegInfo divRegInfo : divRegInfos ){
                    if(regClassInfoVo.getStartAmount()>= divRegInfo.getStartLectureNo() && regClassInfoVo.getEndAmount() <= divRegInfo.getEndLectureNo() ){
                        temp = divRegInfo ;
                        break;
                    }
                }
                if(temp == null ){
                    throw new BusException("【"+courseClass.getClassName()+"】只允许分段报读，如"+divRegInfos.get(0).getStartLectureNo()+"-"+divRegInfos.get(0).getEndLectureNo()+"或"+divRegInfos.get(1).getStartLectureNo()+"-"+divRegInfos.get(1).getEndLectureNo());
                }
                willDivRegInfo = temp ;

                if(!willDivRegInfo.isAvailableReg()){
                    throw new BusException("【"+courseClass.getClassName()+"】第"+willDivRegInfo.getDivNo()+"段未开放报名");
                }
            }


            discountRegClassInfo.setFullReg(willDivRegInfo.getStartLectureNo() == regClassInfoVo.getStartAmount() && willDivRegInfo.getEndLectureNo() == regClassInfoVo.getEndAmount() );
            discountRegClassInfo.setRegDivSegment(willDivRegInfo.getDivNo());
            discountRegClassInfo.setStartAmount(regClassInfoVo.getStartAmount());
            discountRegClassInfo.setEndAmount(regClassInfoVo.getEndAmount());
            int lectureAmount = regClassInfoVo.getEndAmount() - regClassInfoVo.getStartAmount() + 1 ;
            if(lectureAmount == courseClass.getLectureAmount() ){
                discountRegClassInfo.setTotalAmount(courseClass.getStudyingFee());
            }else{
                //计算比例
                discountRegClassInfo.setTotalAmount(calFee(courseClass.getStudyingFee(),lectureAmount,courseClass.getLectureAmount()));
            }
            regClassInfoVo.setTotalAmount(discountRegClassInfo.getTotalAmount());
            discountRegClassInfoList.add(discountRegClassInfo);
            areaTeamIds.add(courseClass.getAreaTeamId());
        }

        if(areaTeamIds.size() > 1 ){
            throw new BusException("不能同时报名多个区域的课程");
        }


        List<DiscountInfoVo> discountInfoList = discountCalculateService.calOfflineDiscount(areaTeamIds.iterator().next() , student,discountRegClassInfoList , null,TimeUtils.now() );

        CreateCourseOrderInfo createCourseOrderInfo = new CreateCourseOrderInfo();
        createCourseOrderInfo.setStudent(student);
        createCourseOrderInfo.setCourseClassList(courseClassList);
        createCourseOrderInfo.setDiscountInfoList(discountInfoList);
        createCourseOrderInfo.setRegClassInfoList(studentRegInfoVo.getRegClassInfoList());
        createCourseOrderInfo.setFrom(studentRegInfoVo.getFrom());

        return createOrder(createCourseOrderInfo,principal);
    }

    @Override
    public void addCourseOrderDiscount(Long courseOrderId , ApplyDiscountVo applyDiscountVo) {

        validApplyDiscount(applyDiscountVo);

        //查找人工申请的优惠
        List<CourseOrderDiscount> srcCourseOrderDiscount = courseOrderDiscountService.findByCourseOrderId(courseOrderId, 1);
        Map<Long,CourseOrderDiscount> discountId2CourseOrderDiscount = BeanUtils.toMap(srcCourseOrderDiscount,"discountId");
        //检查是否有重复申请的情况
        if(discountId2CourseOrderDiscount.containsKey(applyDiscountVo.getDiscountId())){
            throw new BusException("Duplication apply discount for " + discountId2CourseOrderDiscount.get(applyDiscountVo.getDiscountId()).getDiscountName() );
        }


        List<ApplyDiscountVo> srcApplyDiscountVos = new ArrayList<>(srcCourseOrderDiscount.size());
        for(CourseOrderDiscount courseOrderDiscount : srcCourseOrderDiscount ){
            srcApplyDiscountVos.add(toApplyDiscountVo(courseOrderDiscount));
        }

        srcApplyDiscountVos.add(applyDiscountVo);
        updateCourseOrderDiscount(courseOrderId, srcApplyDiscountVos);
    }

    private void updateCourseOrderDiscount(Long courseOrderId, List<ApplyDiscountVo> applyDiscountVos) {

        List<StudentReg> studentRegList = getStudentRegs(courseOrderId);
        List<Long> classIds  = BeanUtils.toList( studentRegList,"classId" );
        Future<List<CourseClass>> futureOfCourseClassList = Threads.dispatch( ()->{
            return courseClassService.findByIds(classIds,CourseClass.BASE_FIELDS);
        });

        Long studentId  = studentRegList.get(0).getStudentId();
        Future<Student> futureOfStudent = Threads.dispatch(()->{
            return studentService.findById(studentId);
        });

        Student student = Threads.get(futureOfStudent);
        List<CourseClass> courseClassList =  Threads.get(futureOfCourseClassList);
        List<DiscountRegClassInfo> discountRegClassInfoList = new ArrayList<>(courseClassList.size());

        Map<Long,StudentReg> classId2StudentReg = BeanUtils.toMap(studentRegList,"classId");


        List<RegClassInfoVo> regClassInfoVoList = buildRegClassInfoVos(courseClassList, discountRegClassInfoList, classId2StudentReg);

        List<DiscountInfoVo> discountInfoList = new ArrayList<>(10);

        Map<Long,ApplyDiscountVo> discountId2ApplyDiscountVo = BeanUtils.toMap(applyDiscountVos,"discountId");

        if(!discountId2ApplyDiscountVo.containsKey(2l)){//员工优惠不能和系统计算叠加
            discountInfoList.addAll(calSystemDiscount(applyDiscountVos, studentRegList, student, courseClassList, discountRegClassInfoList));
        }


        Map<Long, ? extends RegClassDiscountVo> classId2RegClassDiscountMap = shareDiscountToEachCourseClass(discountInfoList ,regClassInfoVoList );
        for(RegClassInfoVo regClassInfoVo : regClassInfoVoList ){
            StudentReg studentReg = classId2StudentReg.get(regClassInfoVo.getClassId());
            RegClassDiscountVo regClassDiscount = classId2RegClassDiscountMap.get(regClassInfoVo.getClassId());
            if(regClassDiscount == null ){
                studentReg.setTotalAmount(regClassInfoVo.getTotalAmount());
                studentReg.setDiscountDetail(StringUtils.EMPTY);
            }else{
                studentReg.setTotalAmount(regClassInfoVo.getTotalAmount().subtract(regClassDiscount.getTotalDiscountAmount()));
                studentReg.setDiscountDetail(StringUtils.join(regClassDiscount.getDiscountRemarkList(),","));
            }
            studentReg.setTotalAmount(BigDecimals.max(BigDecimal.ZERO,studentReg.getTotalAmount()));
        }

        if(discountId2ApplyDiscountVo.containsKey(3l)){//第三方电子优惠券优惠

            ApplyDiscountVo applyDiscountVo = discountId2ApplyDiscountVo.get(3l);

            ThirdpartyDiscountCodeVo thirdpartyDiscountCodeVo = thirdpartyDiscountCodeService.checkCode(courseOrderId, calTotalAmountOfRegClassInfoVo(regClassInfoVoList), applyDiscountVo.getDiscountCode(), student.getStudentId(), student.getName(), student.getStudentNo());

            if(thirdpartyDiscountCodeVo.getDiscountType() != 0 && thirdpartyDiscountCodeVo.getDiscountType() != 1 ){
                throw new BusException("系统只接受立减或满减优惠");
            }

            if (BigDecimals.compare( calTotalAmountOfStudentReg(studentRegList), thirdpartyDiscountCodeVo.getDiscountAmount() ) < 0) {
                throw new BusException("优惠金额大于该订单应付金额，不可使用该优惠卷");
            }


            BigDecimal totalToPay  = calTotalAmountOfStudentReg(studentRegList);
            DiscountInfoVo discountInfoVo = new DiscountInfoVo();
            discountInfoVo.setPriority(1);
            discountInfoVo.setTotalDiscount(thirdpartyDiscountCodeVo.getDiscountAmount());
            discountInfoVo.setClassIds(classIds);
            discountInfoVo.setDiscountId(applyDiscountVo.getDiscountId());
            discountInfoVo.setDiscountName(thirdpartyDiscountCodeVo.getTypeName()+"(优惠"+thirdpartyDiscountCodeVo.getDiscountAmount()+")");
            discountInfoVo.setDiscountType(1);
            discountInfoList.add(discountInfoVo);
            shardDiscountAmount(studentRegList, totalToPay, discountInfoVo.getTotalDiscount() , discountInfoVo);
            applyDiscountVo.setExtra(JSONObject.toJSONString(thirdpartyDiscountCodeVo));

        }

        if(discountId2ApplyDiscountVo.containsKey(2l)){//员工优惠

            ApplyDiscountVo applyDiscountVo = discountId2ApplyDiscountVo.get(2l);

            EmployeeDiscountVo employeeDiscountVo = employeeDiscountService.checkCode(courseOrderId, applyDiscountVo.getDiscountCode(),student.getName(),student.getStudentNo());

            if(employeeDiscountVo.getDiscountType() != 0 ){
                throw new BusException("系统只接受折扣优惠");
            }


            BigDecimal total  = calTotalAmountOfRegClassInfoVo(regClassInfoVoList);
            DiscountInfoVo discountInfoVo = new DiscountInfoVo();
            discountInfoVo.setPriority(1);
            discountInfoVo.setTotalDiscount(total.subtract(BigDecimals.integer(total.multiply(employeeDiscountVo.getDiscountValue().divide(BigDecimal.valueOf(10))))));
            discountInfoVo.setClassIds(classIds);
            discountInfoVo.setDiscountId(applyDiscountVo.getDiscountId());
            discountInfoVo.setDiscountName(employeeDiscountVo.getMainCategoryName()+employeeDiscountVo.getDiscountValue() +"折");
            discountInfoVo.setDiscountType(0);
            discountInfoList.add(discountInfoVo);
            shardDiscountAmount(studentRegList, calTotalAmountOfStudentReg(studentRegList), discountInfoVo.getTotalDiscount() , discountInfoVo);
            applyDiscountVo.setExtra(JSONObject.toJSONString(employeeDiscountVo));

        }


        if(discountId2ApplyDiscountVo.containsKey(1l)){//优惠码

            ApplyDiscountVo applyDiscountVo = discountId2ApplyDiscountVo.get(1l);
            BigDecimal total  = calTotalAmountOfStudentReg(studentRegList);

            DiscountCode discountCode = discountCodeService.checkCode(courseOrderId,applyDiscountVo.getDiscountCode(),calTotalAmountOfStudentReg(studentRegList), courseClassList);

            // 0：金额优惠，1：折扣优惠
            BigDecimal discountAmount = discountCode.getDiscountType() == 0 ? discountCode.getDiscountAmount() :
                    total.multiply(new BigDecimal(1).subtract(discountCode.getDiscountAmount()));
            if (BigDecimals.compare(total, discountAmount) < 0) {
                throw new BusException("优惠金额大于该订单应付金额，不可使用该优惠卷");
            }
            DiscountInfoVo discountInfoVo = new DiscountInfoVo();
            discountInfoVo.setPriority(1);
            discountInfoVo.setAreaTeamId(discountCode.getAreaTeamId());
            discountInfoVo.setTotalDiscount(discountAmount);
            discountInfoVo.setClassIds(classIds);
            discountInfoVo.setDiscountId(applyDiscountVo.getDiscountId());
            discountInfoVo.setDiscountName("优惠券码(" + discountAmount + "元)");
            discountInfoVo.setDiscountType(1);
            discountInfoList.add(discountInfoVo);
            shardDiscountAmount(studentRegList, total, discountAmount, discountInfoVo);

            JSONObject extra = new JSONObject();
            extra.put("name", discountCode.getName() + ":" + student.getName());
            extra.put("discountAmount", discountAmount);
            extra.put("effectiveStartTime", discountCode.getEffectiveStartTime());
            extra.put("effectiveEndTime", discountCode.getEffectiveEndTime());
            extra.put("discountCodeId", discountCode.getDiscountCodeId());
            applyDiscountVo.setExtra(extra.toJSONString());

        }

        if(discountId2ApplyDiscountVo.containsKey(0l)){//特殊优惠
            ApplyDiscountVo applyDiscountVo = discountId2ApplyDiscountVo.get(0l);

            BigDecimal total  = calTotalAmountOfStudentReg(studentRegList);


            if(BigDecimals.compare(total,applyDiscountVo.getDiscountAmount()) < 0 ){
                throw new BusException("特殊优惠不能超过该订单应缴金额");
            }

            DiscountInfoVo discountInfoVo = new DiscountInfoVo();
            discountInfoVo.setPriority(1);
            discountInfoVo.setTotalDiscount(applyDiscountVo.getDiscountAmount());
            discountInfoVo.setClassIds(classIds);
            discountInfoVo.setDiscountId(applyDiscountVo.getDiscountId());
            discountInfoVo.setDiscountName("特殊优惠("+applyDiscountVo.getDiscountAmount()+"元)");
            discountInfoVo.setDiscountType(1);
            discountInfoList.add(discountInfoVo);
            shardDiscountAmount(studentRegList, total , applyDiscountVo.getDiscountAmount(), discountInfoVo);
        }

        //锁定优惠码,感觉有点不靠谱。。。
        for(ApplyDiscountVo applyDiscountVo : applyDiscountVos ){
            if(applyDiscountVo.getDiscountId().equals(1l)){
                discountCodeService.lock(courseOrderId,applyDiscountVo.getDiscountCode());
            }else if(applyDiscountVo.getDiscountId().equals(2l)){
                employeeDiscountService.lock(courseOrderId,applyDiscountVo.getDiscountCode());
            }else if(applyDiscountVo.getDiscountId().equals(3l)){
                thirdpartyDiscountCodeService.lock(courseOrderId,applyDiscountVo.getDiscountCode());
            }
        }


        for(StudentReg studentReg : studentRegList ){
            studentRegService.updateSelectiveById(studentReg);
        }

        CourseOrder courseOrder = new CourseOrder();
        courseOrder.setCourseOrderId(courseOrderId);
        courseOrder.setTotalAmount(calTotalAmountOfRegClassInfoVo(regClassInfoVoList));
        courseOrder.setPayableAmount(calTotalAmountOfStudentReg(studentRegList));
        updateSelectiveById(courseOrder);
        saveCourseOrderDiscount(courseOrderId, applyDiscountVos, discountInfoList);
    }

    private List<DiscountInfoVo> calSystemDiscount(List<ApplyDiscountVo> applyDiscountVos, List<StudentReg> studentRegList, Student student, List<CourseClass> courseClassList, List<DiscountRegClassInfo> discountRegClassInfoList) {

        Validate.notEmpty(courseClassList);
        Validate.notEmpty(studentRegList);

        Collection<Long> discountIdsOfPolicy= Collections2.transform(Collections2.filter(applyDiscountVos, input->input.getDiscountId() > 10 ), input->input.getDiscountId());
        Integer regFrom = studentRegList.get(0).getRegFrom();
        if( regFrom == 0 ){
            return discountCalculateService.calOfflineDiscount(courseClassList.get(0).getAreaTeamId(),student,discountRegClassInfoList,new ArrayList<>(discountIdsOfPolicy) ,studentRegList.get(0).getCreateTime() );
        }else if(regFrom  == 1 ){
            DiscountRegCourseClass discountRegCourseClass = new DiscountRegCourseClass();
            discountRegCourseClass.setStudent(student);
            discountRegCourseClass.setDiscountRegClassInfoList(discountRegClassInfoList);
            Map<Long, List<DiscountInfoVo>> map = discountCalculateService.calOnlineDiscount(courseClassList.get(0).getAreaTeamId(), Arrays.asList(discountRegCourseClass));
            if(map.containsKey(student.getStudentId())){
                return map.get(student.getStudentId());
            }else{
                return Collections.EMPTY_LIST ;
            }
        }else{
            return Collections.EMPTY_LIST ;
        }


    }

    private List<RegClassInfoVo> buildRegClassInfoVos(List<CourseClass> courseClassList, List<DiscountRegClassInfo> discountRegClassInfoList, Map<Long, StudentReg> classId2StudentReg) {
        List<RegClassInfoVo> regClassInfoVoList = new ArrayList<>(courseClassList.size());
        for(CourseClass courseClass : courseClassList){
            StudentReg studentReg = classId2StudentReg.get(courseClass.getClassId());
            DiscountRegClassInfo discountRegClassInfo = new DiscountRegClassInfo();
            BeanUtils.copyProperties(discountRegClassInfo,courseClass);
            RegClassInfoVo regClassInfoVo = new RegClassInfoVo();
            regClassInfoVo.setClassId(courseClass.getClassId());
            regClassInfoVo.setStartAmount(studentReg.getStartAmount());
            regClassInfoVo.setEndAmount(studentReg.getEndAmount());
            discountRegClassInfo.setStartAmount(regClassInfoVo.getStartAmount());
            discountRegClassInfo.setEndAmount(regClassInfoVo.getEndAmount());
            List<DivRegInfo> divRegInfos = divRegService.calDivRegInfo(courseClass.getClassName(), courseClass.getAreaTeamName(), courseClass.getBusTeamName(), courseClass.getPeriodName(), courseClass.getGradeName(), courseClass.getSubjectName(), courseClass.getLectureAmount(), courseClass.getSegmentNo());
            DivRegInfo divRegInfo = divRegInfos.get(0);
            for(DivRegInfo temp : divRegInfos ){
                if(temp.getStartLectureNo() <= studentReg.getStartAmount() && temp.getEndLectureNo() >= studentReg.getEndAmount() ){
                    divRegInfo = temp ;
                    break;
                }

            }
            discountRegClassInfo.setRegDivSegment(divRegInfo.getDivNo());
            discountRegClassInfo.setFullReg(divRegInfo.getStartLectureNo() == regClassInfoVo.getStartAmount() && divRegInfo.getEndLectureNo() == regClassInfoVo.getEndAmount());

            int lectureAmount = regClassInfoVo.getEndAmount() - regClassInfoVo.getStartAmount() + 1 ;
            if(lectureAmount == courseClass.getLectureAmount() ){
                discountRegClassInfo.setTotalAmount(courseClass.getStudyingFee());
            }else{
                //计算比例
                discountRegClassInfo.setTotalAmount(calFee(courseClass.getStudyingFee(),lectureAmount,courseClass.getLectureAmount()));
            }
            regClassInfoVo.setTotalAmount(discountRegClassInfo.getTotalAmount());
            discountRegClassInfoList.add(discountRegClassInfo);
            regClassInfoVoList.add(regClassInfoVo);
        }
        return regClassInfoVoList;
    }

    private void shardDiscountAmount(List<StudentReg> studentRegList, BigDecimal orderPayableAmount, BigDecimal discountAmount, DiscountInfoVo discountInfoVo) {
        BigDecimal discountAmount2 = BigDecimal.ZERO;

        Collections.sort(studentRegList);

        for(int i = 0 ; i < studentRegList.size()  ; i++ ){
            StudentReg studentReg = studentRegList.get(i);
            // 计算优惠分摊比例
            BigDecimal p  = BigDecimals.toFixed2( BigDecimals.calProportion(discountAmount,studentReg.getTotalAmount() , orderPayableAmount ) , 2 ) ;
            if(i == studentRegList.size() - 1 ){
                p = discountAmount.subtract(discountAmount2);
            }else {
                discountAmount2 = discountAmount2.add(p);
            }
            studentReg.setTotalAmount(BigDecimals.max(BigDecimal.ZERO,studentReg.getTotalAmount().subtract(p) ));
            if( StringUtils.isEmpty(studentReg.getDiscountDetail()) ){
                studentReg.setDiscountDetail(concatDiscountDesc(discountInfoVo,p ));
            }else{
                studentReg.setDiscountDetail(studentReg.getDiscountDetail()+","+concatDiscountDesc(discountInfoVo,p ));
            }
        }
    }

    private BigDecimal calTotalAmountOfStudentReg(List<StudentReg> studentRegList) {
        BigDecimal sum = BigDecimal.ZERO ;
        for(StudentReg studentReg : studentRegList ){
            sum = sum.add(studentReg.getTotalAmount());
        }
        return sum ;
    }

    private BigDecimal calTotalAmountOfRegClassInfoVo(List<RegClassInfoVo> regClassInfoVoList) {
        BigDecimal sum = BigDecimal.ZERO ;
        for(RegClassInfoVo regClassInfoVo : regClassInfoVoList ){
            sum = sum.add(regClassInfoVo.getTotalAmount());
        }
        return sum ;
    }

    private void validApplyDiscount(ApplyDiscountVo vo ) {

    }

    private List<StudentReg> getStudentRegs(Long courseOrderId) {
        Example<StudentReg> studentRegExample = StudentReg.createExample();
        studentRegExample.selectProperties("regId","studentId","classId","totalAmount","startAmount" ,"regFrom", "endAmount","createTime");
        studentRegExample.createCriteria().andEqualTo("courseOrderId" , courseOrderId );
        return studentRegService.findListByExample(studentRegExample);
    }

    @Override
    public void delCourseOrderDiscount(Long courseOrderId, Long discountId) {

        CourseOrderDiscount delCourseOrderDiscount = courseOrderDiscountService.findByCourseOrderIdAndDiscountId(courseOrderId, discountId);
        if(delCourseOrderDiscount.getDiscountId().equals(1l)){
            discountCodeService.unlock(delCourseOrderDiscount.getDiscountCode());
        }else if(delCourseOrderDiscount.getDiscountId().equals(2l)){
            employeeDiscountService.unlock(delCourseOrderDiscount.getDiscountCode());
        }else if(delCourseOrderDiscount.getDiscountId().equals(3l)){
            thirdpartyDiscountCodeService.unlock(delCourseOrderDiscount.getDiscountCode());
        }

        List<CourseOrderDiscount> srcCourseOrderDiscount = courseOrderDiscountService.findByCourseOrderId(courseOrderId, 1);
        List<ApplyDiscountVo> srcApplyDiscountVos = new ArrayList<>(srcCourseOrderDiscount.size());
        for(CourseOrderDiscount courseOrderDiscount : srcCourseOrderDiscount ){
            if(courseOrderDiscount.getDiscountId().equals(discountId)){
                continue;
            }
            srcApplyDiscountVos.add(toApplyDiscountVo(courseOrderDiscount));
        }
        updateCourseOrderDiscount(courseOrderId, srcApplyDiscountVos);

    }

    private ApplyDiscountVo toApplyDiscountVo(CourseOrderDiscount courseOrderDiscount) {
        ApplyDiscountVo applyDiscountVo = new ApplyDiscountVo();
        applyDiscountVo.setDiscountId(courseOrderDiscount.getDiscountId());
        applyDiscountVo.setAttachmentUrls(courseOrderDiscount.getAttachmentUrls());
        applyDiscountVo.setDiscountAmount(courseOrderDiscount.getDiscountAmount());
        applyDiscountVo.setDiscountCode(courseOrderDiscount.getDiscountCode());
        applyDiscountVo.setInputParams(courseOrderDiscount.getInputParams());
        return applyDiscountVo;
    }

    private void saveCourseOrderDiscount(Long courseOrderId, List<ApplyDiscountVo> applyDiscountVos, List<DiscountInfoVo> discountInfoList) {

        if(applyDiscountVos == null ){
            applyDiscountVos = Collections.EMPTY_LIST;
        }

        if(discountInfoList == null ){
            discountInfoList = Collections.EMPTY_LIST;
        }

        List<CourseOrderDiscount> courseOrderDiscounts = new ArrayList<>(discountInfoList.size());
        courseOrderDiscountService.deleteByCourseOrderId(courseOrderId);
        Map<Long,ApplyDiscountVo> discountId2ApplyDiscountVo =  null ;
        discountId2ApplyDiscountVo = BeanUtils.toMap(applyDiscountVos,"discountId");

        for(DiscountInfoVo discountInfoVo : discountInfoList){
            CourseOrderDiscount courseOrderDiscount = new CourseOrderDiscount();
            ApplyDiscountVo applyDiscountVo = discountId2ApplyDiscountVo.get(discountInfoVo.getDiscountId());
            courseOrderDiscount.setCourseOrderId(courseOrderId);
            courseOrderDiscount.setDiscountId(discountInfoVo.getDiscountId());
            courseOrderDiscount.setDiscountAmount(discountInfoVo.getTotalDiscount());
            courseOrderDiscount.setDiscountName(discountInfoVo.getDiscountName());
            courseOrderDiscount.setAreaTeamId(discountInfoVo.getAreaTeamId());
            if(applyDiscountVo != null ){
                courseOrderDiscount.setInputParams(applyDiscountVo.getInputParams());
                courseOrderDiscount.setAttachmentUrls(applyDiscountVo.getAttachmentUrls());
                courseOrderDiscount.setFromSrc(1);
                courseOrderDiscount.setDiscountCode(applyDiscountVo.getDiscountCode());
                courseOrderDiscount.setExtra(applyDiscountVo.getExtra());

            }else{
                courseOrderDiscount.setFromSrc(0);
            }

            courseOrderDiscount.setUpdateTime(TimeUtils.now());
            courseOrderDiscount.setCreateTime(TimeUtils.now());
            courseOrderDiscounts.add(courseOrderDiscount);


        }

        if(!courseOrderDiscounts.isEmpty()){

            //根据discountId合并
            Map<Long, List<CourseOrderDiscount>> group = BeanUtils.toGroup(courseOrderDiscounts, "discountId");
            List<CourseOrderDiscount> courseOrderDiscounts2 = new ArrayList<>(group.size());
            for(Map.Entry<Long,List<CourseOrderDiscount>> entry : group.entrySet() ){
                CourseOrderDiscount courseOrderDiscount = new CourseOrderDiscount();
                BeanUtils.copyProperties(courseOrderDiscount,entry.getValue().get(0));
                for(int i = 1 ; i < entry.getValue().size() ; i++ ){
                    courseOrderDiscount.setDiscountAmount(courseOrderDiscount.getDiscountAmount().add(entry.getValue().get(i).getDiscountAmount()));
                }
                courseOrderDiscounts2.add(courseOrderDiscount);
            }

            courseOrderDiscountService.insertBatch(courseOrderDiscounts2);
        }

    }

    @Transactional
    @Override
    public CourseOrder createOrder(CreateCourseOrderInfo createCourseOrderInfo ,Principals.Principal principal) {

        if(principal == null ){
            principal = Principals.getDefaultPrincipal();
        }
        String operator = principal.name ;
        Long operatorId=principal.id;

        Date regDate = createCourseOrderInfo.getRegDate() == null ? TimeUtils.now() : createCourseOrderInfo.getRegDate() ;
        Student student = createCourseOrderInfo.getStudent();
        Integer regFrom = createCourseOrderInfo.getFrom();

        List<CourseClass> courseClassList = createCourseOrderInfo.getCourseClassList();
        Map<Long,? extends RegClassInfoVo> classId2RegClassInfoMap  = BeanUtils.toMap(createCourseOrderInfo.getRegClassInfoList(),"classId");

        if(createCourseOrderInfo.isNeedCheck()){
            Map<Long, IRegRuleService.Result> regRuleCheckResult = regRuleService.regRule(student, courseClassList, createCourseOrderInfo.getFrom());

            for(IRegRuleService.Result result : regRuleCheckResult.values() ){
                if(result.getCode() != 200 ){
                    throw new BusException(result.getMessage());
                }
            }
        }

        RLock lock = redisson.getLock(String.format("studentId:%s", createCourseOrderInfo.getStudent().getStudentId() ));
        if (lock.tryLock()) {
            try {

                //检查报读合法性
                BitSet  hadUsed = new BitSet();
                for(CourseClass courseClass : courseClassList ){
                    if( createCourseOrderInfo.isNeedCheck() && courseClass.getStatus() != 1 ){
                        throw new BusException("【" + courseClass.getClassName()+"】还没开放报名") ;
                    }

                    RegClassInfoVo regClassInfoVo = classId2RegClassInfoMap.get(courseClass.getClassId());


                    //检查报读讲次合法性
                    if( !( regClassInfoVo.getEndAmount() >= regClassInfoVo.getStartAmount() && regClassInfoVo.getStartAmount() > 0 && regClassInfoVo.getEndAmount() <= courseClass.getLectureAmount() ) ){
                        throw new BusException("非法报读讲次") ;
                    }

                    //检查上课时间是否冲突
                    if(createCourseOrderInfo.isNeedCheck()){
                        checkStudyTimeConflict(hadUsed, courseClass);
                    }

                    List<StudentReg> historyRegs = studentRegService.findStudentReg(courseClass.getClassId(), student.getStudentId() );
                    if( createCourseOrderInfo.isNeedCheck() && studentRegService.hadReg( historyRegs ,regClassInfoVo.getStartAmount(),regClassInfoVo.getEndAmount())){
                        if(regFrom == 1 ){
                            throw new BusException(student.getName()+"已经报名过【"+courseClass.getClassAliasName()+"】"+"，请在“我的订单”查看（若未缴费，可直接缴费或撤销），谢谢！") ;
                        }else{
                            throw new BusException(student.getName()+"已经报名过【"+courseClass.getClassName()+"】") ;
                        }

                    }

                    if( (historyRegs == null || historyRegs.isEmpty() ) && !courseClassService.increaseRegAmount(courseClass.getClassId(),courseClass.getRegAmount())){
                        throw new BusException("【报名失败】学位("+courseClass.getClassName()+")已满，请重试!") ;
                    }

                }

                CourseOrder courseOrder = new CourseOrder();
                courseOrder.setCourseOrderId(IdWorker.getId());
                courseOrder.setParentOrderId(createCourseOrderInfo.getParentOrderId());

                courseOrder.setStudentId(createCourseOrderInfo.getStudent().getStudentId());
                courseOrder.setRegFrom(regFrom);
                courseOrder.setStudentName(student.getName());

                Map<Long, RegClassDiscountVo> classId2RegClassDiscountMap = shareDiscountToEachCourseClass(createCourseOrderInfo.getDiscountInfoList(),createCourseOrderInfo.getRegClassInfoList());


                List<StudentReg> studentRegList = new ArrayList<>(courseClassList.size());
                BigDecimal orderTotalAmount = new BigDecimal(0);
                BigDecimal orderPayableAmount = new BigDecimal(0);

                for(CourseClass courseClass : courseClassList ){

                    RegClassInfoVo regClassInfoVo = classId2RegClassInfoMap.get(courseClass.getClassId());
                    StudentReg studentReg = new StudentReg();
                    if (regClassInfoVo.getCounselorId() != null ){
                        studentReg.setCounselorId(regClassInfoVo.getCounselorId());
                        studentReg.setCounselorName(teacherService.findTeacherNameFromFastCache(regClassInfoVo.getCounselorId()));
                    }
                    studentReg.setCourseOrderId(courseOrder.getCourseOrderId());
                    studentReg.setClassId(courseClass.getClassId());
                    studentReg.setClassName(courseClass.getClassName());
                    studentReg.setCreateTime(regDate);
                    studentReg.setUpdateTime(regDate);
                    studentReg.setInitStartAmount(regClassInfoVo.getStartAmount());
                    studentReg.setInitEndAmount(regClassInfoVo.getEndAmount());
                    studentReg.setStartAmount(regClassInfoVo.getStartAmount());
                    studentReg.setEndAmount(regClassInfoVo.getEndAmount());
                    studentReg.setRegFrom(regFrom);//线下
                    studentReg.setChargingStatus(0);
                    studentReg.setRegStatus(0);
                    studentReg.setPayAmount(BigDecimal.ZERO);
                    studentReg.setStudentId(student.getStudentId());
                    studentReg.setStudentName(student.getName());
                    studentReg.setFinancialOwnerId(courseClass.getBusTeamId());
                    studentReg.setAreaTeamId(courseClass.getAreaTeamId());
                    studentReg.setBusTeamId(courseClass.getBusTeamId());
                    studentReg.setPeriodId(courseClass.getPeriodId());
                    studentReg.setSrcTotalAmount(regClassInfoVo.getTotalAmount());
                    studentReg.setOperator(operator);
                    studentReg.setOperatorId(operatorId);
                    studentReg.setSeniorId(courseClass.getSeniorId());
                    studentReg.setSeniorName(courseClass.getSeniorName());
                    studentReg.setRefundAmount(BigDecimal.ZERO);
                    studentReg.setRefundStatus(0);
                    studentReg.setPayLectureAmount(0);
                    RegClassDiscountVo regClassDiscount = classId2RegClassDiscountMap.get(courseClass.getClassId());

                    if(regClassDiscount == null ){
                        studentReg.setTotalAmount(regClassInfoVo.getTotalAmount());
                        studentReg.setDiscountDetail(StringUtils.EMPTY);
                    }else{
                        BigDecimal totalAmount = regClassInfoVo.getTotalAmount().subtract(regClassDiscount.getTotalDiscountAmount());
                        if(createCourseOrderInfo.isInt()){
                            totalAmount = BigDecimals.integer(totalAmount);
                        }
                        studentReg.setTotalAmount(totalAmount);
                        studentReg.setDiscountDetail(StringUtils.join(regClassDiscount.getDiscountRemarkList(),","));
                    }

                    studentReg.setTotalAmount(BigDecimals.max(BigDecimal.ZERO,studentReg.getTotalAmount()));

                    orderTotalAmount = orderTotalAmount.add(regClassInfoVo.getTotalAmount());
                    orderPayableAmount = orderPayableAmount.add(studentReg.getTotalAmount());
                    studentRegList.add(studentReg) ;
                }

                courseOrder.setTotalAmount(orderTotalAmount);
                courseOrder.setPayableAmount(orderPayableAmount);
                courseOrder.setOperator(principal.name);
                courseOrder.setCreateTime(regDate);
                courseOrder.setUpdateTime(regDate);
                insertSelective(courseOrder);
                studentRegService.saveStudentReg(studentRegList) ;
                List<DiscountInfoVo> discountInfoList = createCourseOrderInfo.getDiscountInfoList();
                Long courseOrderId = courseOrder.getCourseOrderId();
                producer.send("create_order_success" ,null, String.valueOf(courseOrderId).getBytes(),2 );//delay 5s
                Threads.dispatch(()->saveCourseOrderDiscount( courseOrderId ,null , discountInfoList));
                return findById(courseOrderId) ;
            } finally {
                lock.unlock();
            }
        }else{
            return null ;
        }


    }



    private void checkStudyTimeConflict(BitSet hadUsed, CourseClass courseClass) {
        if(StringUtils.isEmpty( courseClass.getStudyingTime() ) ){
            return;
        }

        String[] times = courseClass.getStudyingTime().split("-");
        int start =(int)(Utils.parseDate(Utils.formatDate(courseClass.getStartCourseTime(),"yyyyMMdd") + times[0] , "yyyyMMddHH:mm").getTime() / 600000);
        int end =(int)(Utils.parseDate(Utils.formatDate(courseClass.getStartCourseTime(),"yyyyMMdd") + times[1] , "yyyyMMddHH:mm").getTime() / 600000);
        BitSet willUse = new BitSet();
        willUse.set(start,end,true);
        if(hadUsed.intersects(willUse)){
            throw new BusException("【" + courseClass.getClassName() + "】跟其他课程上课时间有冲突") ;
        }
        hadUsed.or(willUse);
    }

    /**
     * 分摊优惠导对应的班级
     * @param discountInfoList
     * @return
     */
    public Map<Long,RegClassDiscountVo> shareDiscountToEachCourseClass(List<DiscountInfoVo> discountInfoList ,List<? extends RegClassInfoVo> regClassInfoList) {
        Map<Long,RegClassDiscountVo> classId2RegClassDiscountMap = new HashMap<>();
        Map<Long,? extends RegClassInfoVo > classId2RegClassInfoVo = BeanUtils.toMap(regClassInfoList,"classId");

        //合并最终优惠
        if(discountInfoList != null ){
            //处理扣减
            for(DiscountInfoVo discountInfoVo : Collections2.filter(discountInfoList,input -> input.getDiscountType() == 1 )){
                //BigDecimal d  = BigDecimals.calProportion(discountInfoVo.getTotalDiscount(),BigDecimal.ONE,BigDecimal.valueOf(discountInfoVo.getClassIds().size())) ;//分摊优惠
                BigDecimal d  = BigDecimals.integer(BigDecimals.calProportion(discountInfoVo.getTotalDiscount(),BigDecimal.ONE,BigDecimal.valueOf(discountInfoVo.getClassIds().size()))) ;//分摊优惠
                BigDecimal sum = BigDecimal.ZERO;
                for(int i = 0 ; i < discountInfoVo.getClassIds().size();i++ ){
                    RegClassDiscountVo regClassDiscountVo = getRegClassDiscountVo(classId2RegClassDiscountMap, discountInfoVo.getClassIds().get(i) );
                    BigDecimal d2 = i == discountInfoVo.getClassIds().size() -1  ? discountInfoVo.getTotalDiscount().subtract(sum) : d;
                    regClassDiscountVo.setTotalDiscountAmount(regClassDiscountVo.getTotalDiscountAmount().add(d2));
                    regClassDiscountVo.getDiscountRemarkList().add(concatDiscountDesc(discountInfoVo, d2 ));
                    sum = sum.add(d2);
                }
            }

            //处理折扣（折上折）
            for(DiscountInfoVo discountInfoVo : Collections2.filter(discountInfoList,input -> input.getDiscountType() == 0 )){
                BigDecimal discountRate = discountInfoVo.getTotalDiscount().divide(BigDecimal.valueOf(100));
                if(BigDecimals.compare(BigDecimal.ONE,discountRate ) < 0 ){
                    throw new BusException("折扣率大于1");
                }

                for(int i = 0 ; i < discountInfoVo.getClassIds().size();i++ ){
                    RegClassDiscountVo regClassDiscountVo = getRegClassDiscountVo(classId2RegClassDiscountMap, discountInfoVo.getClassIds().get(i) );
                    BigDecimal initAmount  = classId2RegClassInfoVo.get(discountInfoVo.getClassIds().get(i)).getTotalAmount().subtract(regClassDiscountVo.getTotalDiscountAmount());
                    BigDecimal amount  = initAmount.multiply(discountRate);
                    BigDecimal discountAmount = BigDecimals.integer(initAmount.subtract(amount));
                    regClassDiscountVo.setTotalDiscountAmount(regClassDiscountVo.getTotalDiscountAmount().add(discountAmount));
                    regClassDiscountVo.getDiscountRemarkList().add(concatDiscountDesc(discountInfoVo, discountAmount));
                }
            }

        }
        return classId2RegClassDiscountMap;
    }

    private RegClassDiscountVo getRegClassDiscountVo(Map<Long, RegClassDiscountVo> classId2RegClassDiscountMap, Long classId) {
        RegClassDiscountVo regClassDiscountVo = classId2RegClassDiscountMap.get(classId);
        if(regClassDiscountVo == null ){
            regClassDiscountVo = new RegClassDiscountVo();
            regClassDiscountVo.setClassId(classId);
            regClassDiscountVo.setTotalDiscountAmount(BigDecimal.ZERO);
            regClassDiscountVo.setDiscountRemarkList(new ArrayList<String>(10));
            classId2RegClassDiscountMap.put(classId ,regClassDiscountVo );
        }
        return regClassDiscountVo;
    }

    private String concatDiscountDesc(DiscountInfoVo discountInfoVo, BigDecimal discountAmount) {
        return discountInfoVo.getDiscountName()+"|"+ BigDecimals.formatBigDecimal( discountAmount ,"0.00")+"|" + discountInfoVo.getDiscountId();
    }


    @Override
    public List<CourseOrder> findCourseOrderByParentOrderId(Long parentOrderId) {
        Example<CourseOrder> example = CourseOrder.createExample();
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("parentOrderId", parentOrderId);
        return findListByExample(example);

    }


    @Transactional
    @Override
    public void confirmPayOrderForOffline(ConfirmPayInfo confirmPayInfo) {

        RLock lock = redisson.getLock(String.format("courseOrder:%d", confirmPayInfo.getCourseOrderId()));

        if( lock.tryLock()){
            try{

                if( posPayService.isPayingForOrder(confirmPayInfo.getCourseOrderId())){
                    throw new BusException("这张订单正在等待确认前一笔支付结果,最多等待5分钟");
                }

                CourseOrder courseOrder = findById(confirmPayInfo.getCourseOrderId());
                if(courseOrder.getRegFrom() == 1 ){
                    throw new BusException("不能处理线上订单") ;
                }

                if(confirmPayInfo.getPays() == null || confirmPayInfo.getPays().isEmpty() ){

                    if(BigDecimals.compare(courseOrder.getPayableAmount() , courseOrder.getPaidAmount()) != 0 ){
                        throw new BusException("支付参数不合法") ;
                    }

                    ConfirmPayInfo.Pay pay = new ConfirmPayInfo.Pay();
                    pay.setAmount(BigDecimal.ZERO);
                    pay.setPayWay(3);
                    confirmPayInfo.setPays(Arrays.asList(pay));

                }

                List<ConfirmPayInfo.Pay> pays = confirmPayInfo.getPays().stream().filter(p -> BigDecimals.compare(p.getAmount(), BigDecimal.ZERO ) >= 0 ).collect(Collectors.toList());


                if(BigDecimals.compare(pays.stream().map(ConfirmPayInfo.Pay::getAmount).reduce(BigDecimal.ZERO,BigDecimal::add),confirmPayInfo.getPayAmount()) != 0 ){
                    throw new BusException("实际支付金额与缴费金额不相等") ;
                }

                confirmPayInfo.getPays().stream().filter(p -> p.getPayWay() == 3).forEach(p -> {
                    MainAccount mainAccount = mainAccountService.findById(courseOrder.getStudentId(), "balanceAmount");
                    if(BigDecimals.compare(p.getAmount(),mainAccount.getBalanceAmount()) > 0 ){
                        throw new BusException("账户余额不够[差"+p.getAmount().subtract(mainAccount.getBalanceAmount())+"]") ;
                    }
                });

                if( !confirmPayInfo.containsPosPay() ){
                    PayInfoOfCourseOrder payInfoOfCourseOrder = new PayInfoOfCourseOrder();
                    payInfoOfCourseOrder.setPrincipal(confirmPayInfo.getPrincipal());
                    payInfoOfCourseOrder.setChargeCampusId(confirmPayInfo.getChargeCampusId());
                    payInfoOfCourseOrder.setPayAmount(confirmPayInfo.getPayAmount());
                    payInfoOfCourseOrder.setPayLectures(confirmPayInfo.getPayLectures());
                    payInfoOfCourseOrder.setPayWays(pays.stream().map( p -> new PayWay(p.getPayWay(),p.getAmount(),null)).collect(Collectors.toList()));
                    payCourseOrder(courseOrder,payInfoOfCourseOrder);
                }else{

                    List<ConfirmPayInfo.Pay> posPays = pays.stream().filter(p -> p.getPayWay() == 1).collect(Collectors.toList());
                    Assert.isTrue(posPays.size() == 1 ,"Oh My Ghost");

                    PosPayRecord posPayRecord = new PosPayRecord();
                    posPayRecord.setStatus(PosPayRecord.STATUS.初始.ordinal());
                    posPayRecord.setRequestPayId(confirmPayInfo.getRequestPayId());
                    posPayRecord.setAmount(posPays.get(0).getAmount().multiply(BigDecimal.valueOf(100)));
                    posPayRecord.setBusOrderId(confirmPayInfo.getCourseOrderId());
                    posPayRecord.setBusType(0);
                    posPayRecord.setChargeCampusId(confirmPayInfo.getChargeCampusId());
                    JSONObject extra = new JSONObject();
                    extra.put("operator",confirmPayInfo.getPrincipal());
                    extra.put("payLectures",confirmPayInfo.getPayLectures());
                    extra.put("otherPays",pays.stream().filter(p -> p.getPayWay() != 1 ).collect(Collectors.toList()) );
                    extra.put("mainAccountId" , courseOrder.getStudentId() );
                    posPayRecord.setExtra(extra.toJSONString());
                    posPayService.savePayRecord(posPayRecord);
                }

            }finally {
                lock.unlock();
            }

            Threads.dispatch(()->studentRegService.setupSetupCounselor(confirmPayInfo.getCourseOrderId(),confirmPayInfo.getCounselorId()));
        }

    }




    /**
     * 1、未缴费直接删除报名纪录和考勤纪录
     * 2、欠费或已缴费，首先把缴费金额退到学生账户，然后再删除报名纪录和考勤纪录
     * @param courseOrderId
     */
    @Transactional
    @Override
    public void cancel(Long courseOrderId ,Principals.Principal principal) {
        if(principal == null ){
            principal = Principals.getDefaultPrincipal() ;
        }

        if( posPayService.isPayingForOrder(courseOrderId)){
            throw new BusException("暂时不能撤销，因为这张订单正在等待确认前一笔支付结果,5分钟后再重试");
        }

        if(posPayService.isLockBalance(courseOrderId)){
            throw new BusException("暂时不能撤销，因为这张订单有锁定余额，请找管理人员先解锁余额");
        }

        Example<CourseOrder> example = CourseOrder.createExample();
        example.createCriteria().andEqualTo("courseOrderId" , courseOrderId ).andIn("chargingStatus" , Arrays.asList(0,1) );
        CourseOrder update = new CourseOrder() ;
        update.setChargingStatus(4);//4:撤销
        boolean b  = updateSelectiveByExample(update,example);
        if(b){
            List<StudentReg> studentRegList = studentRegService.findStudentRegByCourseOrderId(courseOrderId);
            for(StudentReg reg : studentRegList ){
                if(reg.getChargingStatus() == 0 ){
                    studentRegService.cancel(reg.getRegId());
                }else{
                    studentRegService.refundAndCancel(reg.getRegId() , principal );
                }
            }

            Threads.dispatch(()->{
                //解锁优惠码
                CourseOrderDiscount courseOrderDiscount = courseOrderDiscountService.findByCourseOrderIdAndDiscountId(courseOrderId,1l);
                if(courseOrderDiscount != null ){
                    discountCodeService.unlock(courseOrderDiscount.getDiscountCode());
                }

                courseOrderDiscount = courseOrderDiscountService.findByCourseOrderIdAndDiscountId(courseOrderId,2l);
                if(courseOrderDiscount != null ){
                    employeeDiscountService.unlock(courseOrderDiscount.getDiscountCode());
                }

                courseOrderDiscount = courseOrderDiscountService.findByCourseOrderIdAndDiscountId(courseOrderId,3l);
                if(courseOrderDiscount != null ){
                    thirdpartyDiscountCodeService.unlock(courseOrderDiscount.getDiscountCode());
                }
            });

        }else{
            throw new BusException("只有未缴费或欠费的订单才能撤销") ;
        }

    }

    @Transactional
    @Override
    public CourseOrder createUnOnlineOrder(StudentRegInfoVo studentRegInfoVo, Principals.Principal principal) {
        return  this.createOrder(studentRegInfoVo,principal);
    }


    private BigDecimal calFee(BigDecimal fee ,int regLecture , int  totalLecture){
        return BigDecimals.integer( BigDecimals.calProportion(fee,BigDecimal.valueOf(regLecture),BigDecimal.valueOf(totalLecture) )) ;
    }

}